﻿/*
 * Copyright 2012 Rene Prost, Registrite ja Infosüsteemide Keskus 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Services.Description;
using System.Xml;
using System.Xml.Schema;
using log4net;

namespace Xtee.Core.Services.Description
{
     
    public class Description
    {
        private readonly List<XmlSchema> _schemas;
        private readonly List<XmlSchemaImport> _imports;
        private ServiceDescription _serviceDescription;
        private List<XmlQualifiedName> _namespaces;

        public List<XmlQualifiedName> Namespaces
        {
            get
            {
                if (_namespaces != null)
                    return _namespaces;
                
                _namespaces=new List<XmlQualifiedName>();

                _namespaces.AddRange(_serviceDescription.Namespaces.ToArray().ToList());

                var namespaces = Schemas(_serviceDescription.TargetNamespace).ToList().SelectMany(x => x.Namespaces.ToArray().ToList());

                foreach (var @namespace in namespaces)
                {
                    if (_namespaces.All(x => x.Namespace != @namespace.Namespace))
                        _namespaces.Add(@namespace);
                }

                return _namespaces;
            }
        }

    
        public Port Port { get; internal set; } //this is wrong : internal
        
        public Description(List<XmlSchema> schemas)
        {
            _schemas = schemas;
            _imports = _schemas.SelectMany(x => x.Includes.OfType<XmlSchemaImport>()).ToList(); 
        }
         
        public IEnumerable<XmlSchema> Schemas(string targetNamespace)
        {
            var schemas = _schemas.Where(x => x.TargetNamespace == targetNamespace);
            var importedSchemas = _imports.Where(x => x.Schema!=null && x.Schema.TargetNamespace == targetNamespace).Select(x => x.Schema).ToList();
            schemas = schemas.Concat(importedSchemas);
            return  schemas;
        }


        public IEnumerable<XmlSchema> AllWsdlTypesSchemas()
        {
            var importedSchemas = _imports.Where(x => x.Schema != null).Select(x => x.Schema).ToList();
            return _schemas.Concat(importedSchemas);
        }

        public int ObjectCount(string targetNamespace)
        {
            var typeCount = Schemas(targetNamespace)
                .SelectMany(x=>x.SchemaTypes.Values.Cast<XmlSchemaObject>())
                .Count(x => x.CanGenerateCode());

            var messageCount = Port.TargetNamespace.Namespace==targetNamespace ?  Messages.Count() : 0;

            return typeCount + messageCount; 
        }

        public IEnumerable<Header> Headers
            => Port.Operations.Select(x => x.InputHeader).Concat(Port.Operations.Select(x => x.OutputHeader)); 
        public IEnumerable<Message> Messages
        {
            get
            {
                return this.Port.Operations.Select(x => x.Output)
                        .Concat(this.Port.Operations.Select(x => x.Input));
            }
        }
        
        public static Description Load(WsdlLocation location)
        {
            return Load(location, false);
        }
        public static Description Load(WsdlLocation location, bool justOperations)
        { 
            using (var reader = XmlReader.Create(location.FullPath))
            {
                var description = ServiceDescription.Read(reader, true);
                
                return Create(description, justOperations);
            }
        }

        private static Description Create(ServiceDescription fromDescription, bool justOperations)
        {
            var ports = fromDescription.
                Services.
                Cast<Service>().
                SelectMany(y => y.Ports.Cast<System.Web.Services.Description.Port>()).ToList();

            if (ports.Count > 1)
            {
                throw new ServiceDescriptionException("Only single port expected in wsdl. Found many.");
            }
            if (ports.Count == 0)
            {
                throw new ServiceDescriptionException("Couldn't find port from wsdl.");
            }

            var port = ports.First();
            fromDescription
                .EnsureInTargetNamespace(port.Binding, "Binding", "port", port.Name);

           
            var schemas = fromDescription.Types.Schemas.ToList();

            var desctipion =  new Description(schemas);
            Port.Create(port, desctipion, justOperations);
            desctipion._serviceDescription = fromDescription;
            
            return desctipion;
        }

        public System.Web.Services.Description.Message GetHeaderMessage(XmlQualifiedName qualifiedName)
        {
            var headerMessage= _serviceDescription.Messages.Cast<System.Web.Services.Description.Message>()
                    .First(x => x.Name == qualifiedName.Name);

            if (headerMessage != null)
                return headerMessage;
            
            throw new Exception("Couldn't locate header message "+qualifiedName);
                
        }
       
    }
}